package com.atguigu.tingshu.common.login;


import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.user.UserInfo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@Aspect
@Component
@Slf4j
public class LoginAspect {
    @Autowired
    private RedisTemplate redisTemplate;
    //  底层实现aop的原理就是动态代理： 通知
    //  在切面类中自定义一个方法 该方法被@around(annotation="注解的全类路径名")
    //  @Around("@annotation(com.atguigu.tingshu.common.login.TsLogin)")
    /*在定义环绕通知方法的时候,需要注意:
        1,方法的返回值必须是Object
	    2,方法的形参列表必须是ProceedingJoinPoint 用于获取目标方法
	    3,方法必须手动声明Throwable异常
	    4,必须手动调用目标方法*/
    @Around("execution(* com.atguigu.tingshu.*.api.*.*(..)) && @annotation(tsLogin)")
    public Object around(ProceedingJoinPoint joinPoint, TSLogin tsLogin) throws Throwable {
        try {
            // 这里放置可能抛出异常的代码
            //  需要判断当前这个用户是否登录！ 一处登录，处处使用 --- 单点登录SSO; 1. 用户名，密码；2. 微信小程序登录 3. 第三方授权 4. 手机验证码
            //  登录过程： 利用微信小程序登录接口--通过前端传递的code码；调用api能够获取 openId【每个人的微信都有一个唯一的】;
            //  查询用户表；有登录成功-还会将这个用户信息保存到redis，没有注册！
            //  登录成功： 会生成一个token；token就是一个uuid; 将这个token返回给前端；并将这个token 保存到请求头;
            //  重点判断请求头中是否有token就可以了！前端还会将token存储到浏览器本地缓存中！ *****

            // 获取当前请求的RequestAttributes，这允许我们访问与当前HTTP请求相关的属性。
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            // 将RequestAttributes强制转换为ServletRequestAttributes，以便我们可以访问特定于Servlet的请求属性。
            ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
            // 从ServletRequestAttributes中获取HttpServletRequest对象，这让我们能够访问HTTP请求的所有细节。
            HttpServletRequest request = attributes.getRequest();
            //  获取到token数据.
            String token = request.getHeader("token");
            //  编写用户在访问需要登录的业务时，没有登录，则跳转到登录业务逻辑.
            if (tsLogin.required()) {
                //  必须要登录！ 如何没有登录。则跳转!
                if (StringUtils.isEmpty(token)) {
                    //  说明没有登录; 则抛出一个异常，页面根据这个异常就会跳转到登录页面！
                    throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
                }
                //  token 不为空，则说明登录！  还会将这个用户信息保存到redis
                //  此时需要进一步判断缓存中是否有用户信息,如果缓存中没有也不能说明登录！
                //  考虑登录时，缓存的key=token value=userInfo;
                String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
                //  获取缓存中用户数据
                UserInfo userInfo = (UserInfo) this.redisTemplate.opsForValue().get(loginKey);
                //判断
                if (null == userInfo) {
                    throw new GuiguException(ResultCodeEnum.LOGIN_AUTH);
                }
            }

            //token 不为空的时候。将用户Id 存储到本地线程中 便于后台微服务获取用户Id
            if (!StringUtils.isEmpty(token)) {
                //组成缓存key
                String loginKey = RedisConstant.USER_LOGIN_KEY_PREFIX + token;
                //获取缓存中用户数据
                UserInfo userInfo = (UserInfo) this.redisTemplate.opsForValue().get(loginKey);
                if (null != userInfo) {
                    //存储用户Id
                    AuthContextHolder.setUserId(userInfo.getId());
                }
            }
            //执行方法体;
            return joinPoint.proceed();

        } finally {
            //这里的代码将无论如何都会被执行
            //删除本地数据
            AuthContextHolder.removeUserId();
        }
    }
}
